package epoch.game.core.script.java.loader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.logging.log4j.Logger;

import epoch.game.core.common.Loggers;
import epoch.game.core.script.java.ScriptConfig;
import epoch.game.core.script.java.ScriptLoadResult;

/**
 * 脚本加载类
 *
 * @author will
 */
public class ScriptLoader {

	private static Logger log = Loggers.SCRIPT_LOGGER;

	private URLClassLoader classLoader = null;

	public URLClassLoader getClassLoader() {
		return classLoader;
	}

	/**
	 * 加载所有脚本class
	 *
	 * @param scriptConfig
	 */
	public ScriptLoadResult load(ScriptConfig scriptConfig) {
		ScriptLoadResult result = new ScriptLoadResult();
		try {
			String entryScript = scriptConfig.getEntryScript();
			String baseScriptDir = scriptConfig.getScriptBaseDir();
			if (entryScript == null || entryScript.isEmpty() || entryScript.length() < 6) {
				throw new IllegalArgumentException("scriptConfig.entryScript is illegal, can not be null and length must be greater than 6!");
			}
			String entryPath = entryScript.replace(".", "/") + ".class";
			if (baseScriptDir.endsWith(".jar")) {
				// 脚本已jar包方式提供
				JarFile jarFile = new JarFile(baseScriptDir);
				HashSet<String> scriptNames = new HashSet<>();
				ClassLoader mainLoader = this.getClass().getClassLoader();
				URL scriptUrl = new File(baseScriptDir).toURI().toURL();
				log.debug("scriptUrl:" + scriptUrl);
				URL[] urls = new URL[] { scriptUrl };
				if (mainLoader instanceof URLClassLoader) {
					urls = ((URLClassLoader) mainLoader).getURLs();
					List<URL> urlList = new ArrayList<URL>();
					urlList.addAll(Arrays.asList(urls));
					urlList.add(scriptUrl);
					urls = urlList.toArray(urls);
				}
				// 从此jar包 得到一个枚举类
				Enumeration<JarEntry> entries = jarFile.entries();
				// 同样的进行循环迭代
				while (entries.hasMoreElements()) {
					JarEntry element = entries.nextElement();
					String fileName = element.getName();
					int lastIndex = fileName.lastIndexOf(".");
					if (lastIndex == -1) {
						continue;
					}
					String fileExt = fileName.substring(lastIndex);
					if (!".class".equals(fileExt)) {
						continue;
					}
					String className = fileName.substring(0, lastIndex).replace("/", ".");
					scriptNames.add(className);
				}
				@SuppressWarnings("resource")
				ScriptClassLoader tempClassLoader = new ScriptClassLoader(urls, jarFile, scriptNames);
				for(String className : scriptNames){
					try {
						Class<?> loadClass = tempClassLoader.loadClass(className);
						if (loadClass == null) {
							// 出错了
							throw new RuntimeException("load script class err! customDefineClass:className=" + className);
						}
					} catch (Exception ex) {
						result.setErrMsg(ex.getMessage());
						result.setResultCode(1);
						return result;
					}
				}
				classLoader = tempClassLoader;
				log.info("ScriptLoader.load2: classLoader=" + classLoader);
				result.setResultCode(0);
				return result;
			}
			//以下是以.class文件方式加载
			String scriptDir = baseScriptDir + "/" + entryPath;
			if (scriptDir == null || scriptDir.isEmpty()) {
				throw new IllegalArgumentException("scriptConfig.entryScript is illegal!");
			}
			log.debug("scriptDir:" + scriptDir);
			URL url = null;
			try {
				url = new File(baseScriptDir).toURI().toURL();
			} catch (MalformedURLException e2) {
				log.error(e2, e2);
			}
			URL url2 = null;
			log.debug("script url 1:" + url);
			if (url == null) {
				url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
				log.debug("script url 2:" + url);
				String realPath = url.getFile();
				if (realPath.endsWith(".jar")) {
					url2 = url;
					realPath = realPath.substring(0, realPath.lastIndexOf("/") + 1);
				}
				File dir = new File(realPath);
				try {
					url = dir.toURI().toURL();
				} catch (MalformedURLException e) {
					throw new IllegalArgumentException("script base dir err!");
				}
				log.debug("script url 3:" + url);
			}
			ClassLoader mainLoader = this.getClass().getClassLoader();
			URL scriptUrl = new File(baseScriptDir).toURI().toURL();
			log.debug("scriptUrl:" + url);
			URL[] urls = null;
			if (url2 != null) {
				urls = new URL[] { url, url2, scriptUrl };
			} else {
				urls = new URL[] { url, scriptUrl };
			}
			if (mainLoader instanceof URLClassLoader) {
				urls = ((URLClassLoader) mainLoader).getURLs();
				List<URL> urlList = new ArrayList<URL>();
				urlList.addAll(Arrays.asList(urls));
				urlList.add(scriptUrl);
				urls = urlList.toArray(urls);
			}
			HashSet<String> scriptNames = new HashSet<String>();
			try {
				String[] strs = entryScript.split("\\.");
				if (strs.length > 0) {
					String firstPackageName = "";
					firstPackageName = strs[0];
					scriptNames = findAllScripts(new File(baseScriptDir + File.separator + firstPackageName), scriptNames, firstPackageName);
				} else {
					scriptNames = findAllScripts(new File(baseScriptDir), scriptNames, "");
				}

			} catch (Exception e) {
				log.error("load script faild:scan class file err!" + e, e);
				result.setErrMsg(e.getMessage());
				result.setResultCode(1);
				return result;
			}
			ScriptClassLoader tempClassLoader = new ScriptClassLoader(urls, baseScriptDir, scriptNames);
			log.info("ScriptLoader.load1: classLoader=" + tempClassLoader);
			// 重新从文件加载类
			try {
				String[] strs = entryScript.split("\\.");
				if (strs.length > 0) {
					String firstPackageName = "";
					firstPackageName = strs[0];
					recuteDefineClass(tempClassLoader, new File(baseScriptDir + File.separator + firstPackageName), firstPackageName);
				} else {
					recuteDefineClass(tempClassLoader, new File(baseScriptDir), "");
				}
			} catch (Exception e) {
				log.error("load script faild:load class file err!" + e, e);
				result.setErrMsg(e.getMessage());
				result.setResultCode(1);
				return result;
			}
			try {
				// 加载入口类
				recuteLoadClass(tempClassLoader, new File(scriptDir), entryScript);
			} catch (Exception e) {
				log.error("load script faild:load entryScript err!" + e, e);
				result.setErrMsg(e.getMessage());
				result.setResultCode(1);
				return result;
			}
			classLoader = tempClassLoader;
			log.info("ScriptLoader.load2: classLoader=" + classLoader);
			result.setResultCode(0);
		} catch (Exception e) {
			log.error("load script faild:load script err!" + e, e);
			result.setErrMsg(e.getMessage());
			result.setResultCode(1);
		}
		return result;
	}

	/**
	 * 扫描出所有的脚本类，这些类都需要使用自定义的classloader进行加载
	 * 
	 * @param file
	 * @param scriptNames
	 * @param className
	 * @return
	 * @throws IOException
	 */
	private HashSet<String> findAllScripts(File file, HashSet<String> scriptNames, String className) throws IOException {
		if (file == null) {
			return scriptNames;
		}
		if (!file.exists()) {
			String message = "file not exists :" + file.getAbsolutePath();
			log.error(message);
			throw new FileNotFoundException(message);
		}
		if (file.isDirectory()) {
			String currentPackagePrefix = "";
			if (className != null && !className.isEmpty()) {
				currentPackagePrefix = className + ".";
			}
			for (File child : file.listFiles()) {
				String clsName = currentPackagePrefix + child.getName().replace(".class", "");
				findAllScripts(child, scriptNames, clsName);
			}
			return scriptNames;
		}
		String fileName = file.getName();
		int lastIndex = fileName.lastIndexOf(".");
		if (lastIndex == -1) {
			return scriptNames;
		}
		String fileExt = fileName.substring(lastIndex);
		if (!".class".equals(fileExt)) {
			return scriptNames;
		}
		scriptNames.add(className);
		return scriptNames;
	}

	/**
	 * 加载class类文件
	 *
	 * @param file
	 * @param className
	 */
	private void recuteDefineClass(ScriptClassLoader loader, File file, String className) throws Exception {
		if (file == null) {
			return;
		}
		if (!file.exists()) {
			String message = "file not exists :" + file.getAbsolutePath();
			log.error(message);
			throw new FileNotFoundException(message);
		}
		if (file.isDirectory()) {
			String currentPackagePrefix = "";
			if (className != null && !className.isEmpty()) {
				currentPackagePrefix = className + ".";
			}
			for (File child : file.listFiles()) {
				String clsName = currentPackagePrefix + child.getName().replace(".class", "");
				recuteDefineClass(loader, child, clsName);
			}
			return;
		}
		String fileName = file.getName();
		int lastIndex = fileName.lastIndexOf(".");
		if (lastIndex == -1) {
			return;
		}
		String fileExt = fileName.substring(lastIndex);
		if (!".class".equals(fileExt)) {
			return;
		}
		try {
			Class<?> loadClass = loader.customDefineClass(className, file);
			// Class<?> loadClass = loader.loadClass(className);
			if (loadClass == null) {
				// 出错了
				throw new RuntimeException("load script class err!");
			}
		} catch (IOException e) {
			log.error("load class err. classname=" + className, e);
			throw new Exception("load class err. classname=" + className + ",msg:" + e.getMessage(), e);
		}
	}

	/**
	 * 加载class类文件
	 *
	 * @param file
	 * @param className
	 */
	private void recuteLoadClass(ScriptClassLoader loader, File file, String className) throws Exception {
		try {
			if (file == null) {
				return;
			}
			if (!file.exists()) {
				log.error("file not exists :" + file.getAbsolutePath());
				return;
			}
			if (file.isDirectory()) {
				String currentPackagePrefix = "";
				if (className != null && !className.isEmpty()) {
					currentPackagePrefix = className + ".";
				}
				for (File child : file.listFiles()) {
					String clsName = currentPackagePrefix + child.getName().replace(".class", "");
					// if(!clsName.equals(className)){//入口类最后加载
					recuteLoadClass(loader, child, clsName);
					// }else{
					// entryClassFile = child;
					// }
				}
				return;
			}
			String fileName = file.getName();
			int lastIndex = fileName.lastIndexOf(".");
			if (lastIndex == -1) {
				return;
			}
			String fileExt = fileName.substring(lastIndex);
			if (!".class".equals(fileExt)) {
				return;
			}
			Class<?> loadClass = loader.loadClass(className);
			if (loadClass == null) {
				throw new ClassNotFoundException();
			}
		} catch (Exception ex) {
			log.error("load class err. classname=" + className, ex);
			throw new Exception("load class err. classname=" + className + ",msg:" + ex.getMessage(), ex);
		}
	}
}
